Source: models/graph-model.js

/**
 * @auther Yuxin Ma
 * @module models/graph-model
 * @exports Graph
 */

'use strict';
var $ = require('jquery');
var _ = require('underscore');
var Backbone = require('backbone');
Backbone.$ = $;

var Node = require('./node-model');
var NodeCollection = require('../collections/node-collection');
var Edge = require('./edge-model');
var EdgeCollection = require('../collections/edge-collection');
var egoLoader = require('./demodata/egoloader');

var Graph = Backbone.Model.extend( /** @lends models/Graph.prototype */ {
    /**
     * 图结构
     *
     * @augments Backbone.Model
     * @constructs
     */
    initialize: function() {
        /*
         * TODO: 2015.11.06 先使用方舟的前端读取方法进行填充。等待后端完全实现后在EgoQuery中修改。
         */
    },

    defaults: {
        /**
         * 节点集合。
         * @type {?NodeCollection}
         */
        nodes: null,

        /**
         * 边集合。
         * @type {?EdgeCollection}
         */
        edges: null,

        /**
         * 图的类型。枚举量,为Graph.Type中的一种:{(EGO|PATH|COMM)}。
         * @type {string}
         */
        type: null,

        /**
         * 查询创建的日期。
         * @type {?Date}
         */
        queryDate: null,

        /**
         * 最后修改日期。
         * @type {?Date}
         */
        lastUpdateDate: null,

        __lastVisualDiameter: null,
        __lastVisualScale: 1.0
    },

    /**
     * @method 重载parse方法,用于生成前端数据。
     * @param {*} data
     * @param {*} options
     * @returns {*|{nodes, edges, type, nodeProperties, edgeProperties, queryDate, lastUpdateDate}}
     */
    parse: function(data, options) {
        //this.set('type', data.type);
        if (data.DEBUG_EGO_FRONTEND === true) {
            this.set('DEBUG_EGO_FRONTEND', true);
            this.set('nodes', new NodeCollection());
            this.set('edges', new EdgeCollection());
            return this.__loadDemoEgoGraph(data.diameter);
        }
    },

    __loadDemoEgoGraph: function(diameter) {
        var nodes = new NodeCollection();
        var edges = new EdgeCollection();
        var ego = egoLoader(nodes, edges);
        ego.readEgo(function() {
            ego.layoutEgo(diameter, function(){});
        });

        // 添加未删除标签
        nodes.each(function(model) {
            model.set('__deleted', false);
        });

        edges.each(function(model) {
            model.set('__deleted', false)
        });

        return {
            nodes: nodes,
            edges: edges,
            type: Graph.Type.EGO,
            nodeProperties: ['gender', 'keyFlag', 'level'],
            edgeProperties: ['count', 'type'],
            queryDate: new Date(),
            lastUpdateDate: new Date()
        };
    },

    /**
     * 返回网络类型,为Graph.Type中的一种
     * @returns {String} Graph的类型
     */
    getType: function() {
        return this.get('type');
    },

    /**
     * 判断是否为Ego网络
     * @return {boolean}
     */
    isEgoNetwork: function() {
        return this.get('type') === Graph.Type.EGO;
    },

    /**
     * 根据id号查询Node。
     * @param nodeId
     * @returns {(Node|undefined)}
     */
    getNodeById: function(nodeId) {
        return this.get('nodes').get(nodeId);
    },

    /**
     * 根据id号查询Edge。
     * @param edgeId
     * @returns {(Edge|undefined)}
     */
    getEdgeById: function(edgeId) {
        return this.get('edges').get(edgeId);
    },

    /**
     * 通过设置过滤器来查找节点
     * @param filterOptions 过滤参数
     * @returns {Node[]} 查找到的节点数组
     */
    getNodeByProperty: function(filterOptions) {
        return this.get('nodes').where(filterOptions);
    },

    /**
     * 返回节点的可用属性
     * @returns {string[]}
     */
    getNodeProperties: function() {
        return this.get('nodeProperties');
    },

    /**
     * 返回边的可用属性
     * @returns {string[]}
     */
    getEdgeProperties: function() {
        return this.get('edgeProperties');
    },

    /**
     * 生成所有可用Node的数组。在lazy模式下,会屏蔽所有已删除的节点。
     * @returns {Node[]}
     */
    getNodeArray: function() {
        var nodes = this.get('nodes');
        if (nodes === null) {
            throw new Error('graph not initialized');
        }

        return nodes.filter({__deleted: false});
    },

    /**
     * 生成所有可用Edge的数组。在lazy模式下,会屏蔽所有已删除的边。
     * @returns {Edge[]}
     */
    getEdgeArray: function() {
        var edges = this.get('edges');
        if (edges === null) {
            throw new Error('graph not initialized');
        }

        return edges.filter({__deleted: false});
    },

    /**
     * 查找邻居
     * @param {Node} node - 节点id号,或Node的引用。
     * @param {string} direction - 指定方向,为Graph.Direction中的一个值。
     * @return {array} Node和Edge的数组。
     */
    getNeighbors: function(node, direction) {
        throw new Error('not implemented');
    },

    /**
     * 添加节点
     * @param {Node} node - 新节点
     */
    addNode: function(node) {
        this.get('nodes').add(node);
    },

    /**
     * 添加边
     * @param {Edge} edge - 新边
     */
    addEdge: function(edge) {
        this.get('edges').add(edge);
    },

    ///**
    // * 删除节点
    // * @param {Node} node
    // * @param options
    // */
    removeNode: function(node, options) {
        throw new Error('not implemented');
    },

    ///**
    // * 删除边
    // * @param {Edge} edge
    // * @param options
    // */
    removeEdge: function(edge, options) {
        throw new Error('graph not initialized');
    },

    ///**
    // * 合并节点
    // * @param {Node[]} nodeArray - 需要合并的节点
    // */
    mergeNodes: function(nodeArray) {
        throw new Error('graph not initialized');
    },

    /**
     * 重新计算Node的位置。
     * @param {object} options 与图类型相对应的视觉参数
     */
    layout: function(options) {

        var type = this.get('type');

        /*
         * 根据图的类型,检查layout所需参数是否完整。如检出错误则抛出Error对象。
         */
        switch (type) {
            case Graph.Type.EGO:
                if (options.diameter === undefined || typeof(options.diameter) !== 'number')
                    throw new Error('missing diameter or wrong type');
                break;
            case Graph.Type.PATH:
                break;
        }

        /*
         * 测试用
         * TODO: 后端完成后删除
         */
        if (this.get('DEBUG_EGO_FRONTEND') === true) {
            this.set(this.__loadDemoEgoGraph(options.diameter));
            this.set('__lastVisualDiameter', options.diameter);
        }
    },

    /**
     * 返回是否填充过后端布局数据。
     * @returns {boolean}
     */
    hasServerSideLayout: function() {
        return true;
    },

    ///**
    // * 对整个Graph的位置进行放缩。
    // * @param options {Object} 与图类型相对应的视觉参数:
    // * {
    // *     scale: {number} 放大系数。1.0为原大。
    // * }
    // */
    scale: function(options) {

        /**
         * TODO: 将scale方法放到graphvis.layout.ego中
         */

        throw new Error('method not implemented');

        //if (this.get('DEBUG_EGO_FRONTEND') === true) {
        //    console.log(this.get('__lastVisualDiameter'));
        //    console.log(options.scale);
        //    console.log(options.scale * this.get('__lastVisualDiameter'));
        //
        //    // 2015.11.30 修改:不能直接使用__loadDemoEgoGraph方法创建新的NodeCollection实例,而是获取新结果后手动set原NodeCollection。
        //    //this.set(this.__loadDemoEgoGraph(options.scale * this.get('__lastVisualDiameter')));
        //    var scaledResult = this.__loadDemoEgoGraph(options.scale * this.get('__lastVisualDiameter'));
        //    var oldNodes = this.get('nodes');
        //
        //    scaledResult.nodes.each(function(d) {
        //        oldNodes.get(d.id).set({x: d.get('x'), y: d.get('y')});
        //    });
        //
        //    this.set('__lastVisualScale', options.scale);
        //    this.set('lastUpdateDate', new Date());
        //}
    },

    getScale: function() {
        return this.get('__lastVisualScale');
    }
}, {

    // 静态常量成员

    /**
     * 边的方向:{(IN|OUT|BOTH)}
     */
    Direction: {
        IN: 'in',
        OUT: 'out',
        BOTH: 'both'
    },

    /**
     * Graph的类型:{(EGO|PATH|COMM)}
     */
    Type: {
        EGO: 'ego',
        PATH: 'path',
        COMM: 'comm'
    }
});


module.exports = Graph;